﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using SystemGroup.Framework.Common;
using MN.Framework.Extensions;
using MN.Framework.MetaData;

namespace MN.Framework.Common
{
    //public struct ColumnInfo
    //{
    //    public ColumnInfo(Type type, string name, string title)
    //    {
    //        ColumnType = type;
    //        Name = name;
    //        Title = title;
    //        ReadOnly = false;
    //    }
    //    public ColumnInfo(Type type, string name, string title, bool readOnly)
    //    {
    //        ColumnType = type;
    //        Name = name;
    //        Title = title;
    //        ReadOnly = readOnly;
    //    }
    //    public Type ColumnType;
    //    public string Name;
    //    public string Title;
    //    public bool ReadOnly;
    //}
    public interface IBaseEntity
    {
        int ID { get; set; }
        void GetColumns(List<ColumnInfo> columns);
    }
    public abstract class BaseEntity : IBaseEntity
    {
        #region Property
        public abstract int ID { get; set; }
        private Dictionary<string, List<ValidationRule>> propertyValidationRuleMap;
        #endregion
        public BaseEntity()
        {
            propertyValidationRuleMap = new Dictionary<string, List<ValidationRule>>();
            SetDefaultValues();

        }

        public virtual void GetColumns(List<ColumnInfo> columns)
        {
            DefaultGenerateColumns(columns);
        }

        private void DefaultGenerateColumns(List<ColumnInfo> columns)
        {
            var baseTypeProperties =
                this.GetType()
                    .GetProperties()
                    .Where(property => !property.PropertyType.IsInterface && !property.GetMethod.IsVirtual).ToList();
            var virtualProperties =
                this.GetType()
                    .GetProperties()
                    .Where(property => property.PropertyType.IsInterface || property.GetMethod.IsVirtual).ToList();

            foreach (PropertyInfo property in virtualProperties)
            {
                var foreignKeyName =
                   Attribute.GetCustomAttributes(property, typeof(ForeignKeyAttribute))
                       .Select(at => ((ForeignKeyAttribute)at).Name)
                       .SingleOrDefault();
                if (!string.IsNullOrEmpty(foreignKeyName))
                {
                    var bPT = baseTypeProperties.SingleOrDefault(p => p.Name.ToLower() == foreignKeyName.ToLower());
                    if (bPT != null)
                    {
                        var column = GetReferenceColumn(bPT);
                        column.BaseType = property.PropertyType;
                        columns.Add(column);
                        baseTypeProperties.Remove(bPT);
                    }
                }
            }


            columns.AddRange(baseTypeProperties.Select(property => GetTextColumn(property)));
        }

        private ColumnInfo GetTextColumn(PropertyInfo property)
        {
            var title =
                  Attribute.GetCustomAttributes(property, typeof(DisplayAttribute))
                      .Select(at => ((DisplayAttribute)at).Name)
                      .SingleOrDefault();
            var length = Attribute.GetCustomAttributes(property, typeof(MaxLengthAttribute))
                .Select(at => ((MaxLengthAttribute)at).Length)
                .SingleOrDefault();

            if (!property.PropertyType.IsInterface && !property.GetMethod.IsVirtual)
            {
                var column = new TextColumnInfo(property.Name, string.IsNullOrEmpty(title) ? property.Name : title);

                if (length > 0)
                {
                    if (length < 10)
                        column.Size = ColumnSize.Small;
                    else if (length < 50)
                        column.Size = ColumnSize.Standard;
                    else if (length < 250)
                        column.Size = ColumnSize.Larg;
                    else if (length < 1000)
                        column.Size = ColumnSize.XLarg;
                    else if (length > 1000)
                        column.Size = ColumnSize.XXLarg;
                }
                return column;
            }
            return null;
        }
        private ColumnInfo GetReferenceColumn(PropertyInfo property)
        {
            var title =
                  Attribute.GetCustomAttributes(property, typeof(DisplayAttribute))
                      .Select(at => ((DisplayAttribute)at).Name)
                      .SingleOrDefault();

            var column = new ReferenceColumnInfo(property.Name, string.IsNullOrEmpty(title) ? property.Name : title);

            return column;

        }
        public virtual void RegisterValidationRules()
        {

        }
        public void AddRule(string propertyName, ValidationRule rule)
        {
            if (!this.propertyValidationRuleMap.ContainsKey(propertyName))
            {
                this.propertyValidationRuleMap.Add(propertyName, new List<ValidationRule>());
            }

            if (!this.propertyValidationRuleMap[propertyName].Exists(v => v.GetType() == rule.GetType()))
                this.propertyValidationRuleMap[propertyName].Add(rule);

        }
        public bool Validate()
        {
            this.RegisterValidationRules();
            bool result = true;
            using (Dictionary<string, List<ValidationRule>>.Enumerator enumerator = this.propertyValidationRuleMap.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    //Action<ValidationRule> action = null;
                    KeyValuePair<string, List<ValidationRule>> item = enumerator.Current;
                    foreach (var rule in this.propertyValidationRuleMap[item.Key].AsEnumerable())
                    {
                        rule.ErrorMessage = string.Empty;
                        result = rule.IsValid(this, item.Key) && result;
                    }
                    //this.propertyValidationRuleMap[item.Key].ForEach(delegate(ValidationRule f)
                    //{
                    //    f.ErrorMessage = "";
                    //});
                    //if (action == null)
                    //{
                    //    action = delegate(ValidationRule f)
                    //    {
                    //        result = f.IsValid(this, item.Key) && result;
                    //    };
                    //}
                    //this.propertyValidationRuleMap[item.Key].ForEach(action);
                }
            }
            return result;
        }
        public virtual void SetDefaultValues()
        {

        }
        public string GetErrorMessages()
        {
            Action<ValidationRule> action = null;
            StringBuilder errorStringBuilder = new StringBuilder();
            foreach (KeyValuePair<string, List<ValidationRule>> pair in this.propertyValidationRuleMap)
            {
                foreach (var rule in pair.Value)
                    if (!string.IsNullOrEmpty(rule.ErrorMessage))
                        errorStringBuilder.AppendLine(rule.ErrorMessage);
                //if (action == null)
                //{
                //    action = delegate(ValidationRule f)
                //    {
                //        if (!string.IsNullOrEmpty(f.ErrorMessage))
                //        {
                //            errorStringBuilder.AppendLine(f.ErrorMessage);
                //        }
                //    };
                //}
                //this.propertyValidationRuleMap[pair.Key].ForEach(action);
            }
            return errorStringBuilder.ToString();
        }

        public string DefaultFilterField()
        {
            var attr = (SearchFieldsAttribute)Attribute.GetCustomAttribute(this.GetType(), typeof(SearchFieldsAttribute));
            if (attr != null && attr.GetSearchFields().Any())
                return attr.GetSearchFields().FirstOrDefault();
            else
                return string.Empty;
        }
        public string FirstOrDefaultDisplayField()
        {
            var attr = (DisplayFieldAttribute)Attribute.GetCustomAttribute(this.GetType(), typeof(DisplayFieldAttribute));
            if (attr != null && attr.GetDisplayFields().Any())
                return attr.GetDisplayFields().FirstOrDefault();
            else
            {
                List<ColumnInfo> columns = new List<ColumnInfo>();
                GetColumns(columns);
                if (columns.Count > 1)
                    return columns[1].Name;
            }
            return string.Empty;
        }

        public string ToXml()
        {
            string res = "<" + (this.GetType().BaseType).FullName + ">";
            List<ColumnInfo> columns = new List<ColumnInfo>();
            this.GetColumns(columns);
            foreach (var clmn in columns)
            {
                if (clmn.Type != ColumnType.Refrence && clmn.Type != ColumnType.Image)
                    res += "<" + clmn.Name + ">" + this.PropertyByName(clmn.Name).GetValue(this) + "</" + clmn.Name + ">";
            }
            return res += "</" + (this.GetType().BaseType).FullName + ">";
        }
        public List<dynamic> ToList(BaseContext dbContext)
        {
            return dbContext.Set(this.GetType()).AsEnumerable().ToList();
        }
        public List<T> ToList<T>(BaseContext dbContext)
        {
            return dbContext.Set(this.GetType()).AsEnumerable().Select(e => (T)e).ToList();
        }
        //{
        //    NumericColumnInfo item = new NumericColumnInfo("ID", "شناسه")
        //    {
        //        Visible = false,
        //        HasThousandSeparator = false
        //    };
        //    columns.Add(item);
        //}
    }
}
